vite+vue3 知识体系

知识点

(一) 创建项目

npm init vite@latest

(二) mvvm 框架介绍

  1. m model 后台数据
  2. vm vue
  3. v view 视图, 展示出来的页面

(三) 添加element-plus

  1. 安装依赖 npm install element-plus --save

(四) 变量和响应式变量

  1. 变量可以直接放入 template 内显示在页面上, 但不具备响应式效果
<template>
  {{ msg }}
</template>

<script setup>
import HelloWorld from "./components/HelloWorld.vue";
let msg = "hahaha";

setTimeout(() => {
  msg = "2222";
}, 2000);
</script>
  1. 响应式变量
  • 使用 ref 创建响应式变量
  • 使用 xxx.value 或者 String(xxx)获取响应式变量的值
<template>
  {{ msg }}
</template>

<script setup>
import { ref } from "vue";
let msg = ref("hahaha");

setTimeout(() => {
  msg.value = "heieheiehieie";
}, 2000);
</script>

(六) 响应式对象

<template>
<span>{{ user.username }}</span>

  <hr>
  <button @click="setUsername">修改</button>
</template>

<script setup>
import { ref, reactive } from "vue";

const user = reactive({
  username: "老胡",
  age: 100,
});
const setUsername = () => {
  user.username = "胡老";
};
</script>

## 事件

1. .native 修饰符不再需要, 可以直接使用原生事件
2. $event

​```vue
<template>
  <span>{{ user.username }}</span>
  <hr />
  <button @click="setUsername('李四', $event)">修改</button>
</template>

<script setup>
import { ref, reactive } from "vue";

const user = reactive({
  username: "老胡",
  age: 100,
});
const setUsername = (newName, event) => {
  user.username = newName;
  console.log(event);
};
</script>

(七) $refs

vue3.2 如何获取子组件实例??

(八) 计算属性

  1. 使用普通方法,每使用一次就调用一次方法
  2. 使用 computed 可以缓存结果
  3. 对计算属性进行修改

1.普通方法

方法执行了三次

<template>
  <span>{{ str }}</span> <br />
  <span>{{ reverseStr(str) }}</span>
  <span>{{ reverseStr(str) }}</span>
  <span>{{ reverseStr(str) }}</span>
</template>

<script setup>
import { ref } from "vue";
const str = ref("我怕太太");

let count = 0;
const reverseStr = (str) => {
  console.log(++count);
  return str.split("").reverse().join("");
};
</script>

2.计算属性

方法只执行一次

<template>
  <span>{{ str }}</span> <br />
  <span>{{ str2 }}</span>
  <span>{{ str2 }}</span>
  <span>{{ str2 }}</span>
</template>

<script setup>
import { ref, computed } from "vue";
const str = ref("我怕太太");

let count = 0;
let str2 = computed(() => {
  console.log(++count);
  return str.value.split("").reverse().join("");
});
</script>

3.对计算属性进行修改

<template>
  <span>{{ money }}</span> <br />
  <span>{{ money2 }}</span> <br />
  <button @click="setMoney">修改</button>
</template>

<script setup>
import { ref, computed } from "vue";
const money = ref(100);
let money2 = computed({
  get() {
    return money.value.toFixed(2);
  },
  set(value) {
    money.value = value;
  },
});

const setMoney = () => {
  money2.value = 200;
};
</script>

(九) watch和watchEffect

watchEffect

  1. 用途: 改一个, 触发多个改变
  2. 监听多个
  3. 监听对象

1.用途

<template>
  <span>{{ zhangsan }}</span> <br />
  <span>{{ lisi }}</span> <br />
  <button @click="setMoney(100)">修改</button>
</template>

<script setup>
import { ref, watch } from "vue";

const zhangsan = ref(0);
const lisi = ref(0);
const money = ref(0);

watch(money, (newVal, oldVal) => {
  zhangsan.value = newVal * 0.6;
  lisi.value = newVal * 0.4;
});

const setMoney = (num) => {
  money.value = num;
};
</script>

2.监听多个属性

<template>
  <span>{{ zhangsan }}</span> <br />
  <span>{{ lisi }}</span> <br />
  <button @click="setMoney(100)">修改</button>
</template>

<script setup>
import { ref, watch } from "vue";

const zhangsan = ref(0);
const lisi = ref(0);
const money = ref(0);
const money2 = ref(0);

watch([money, money2], (newVal, oldVal) => {
  console.log(newVal);
  console.log(oldVal);
});

const setMoney = (num) => {
  money.value = num;
  money2.value = num;
};
</script>

3.监听对象

因为对象是引用数据类型, 所以新旧值都一样,vue3 不会保留原对象的副本

  1. 监听整个对象
  2. 监听对象的某个属性

1.监听整个对象

<template>
  <span>{{ user.name }}</span> <br />
  <span>{{ user.age }}</span> <br />
</template>

<script setup>
import { reactive, watch } from "vue";

const user = reactive({
  name: "张三",
  age: 20,
});
watch(user, (newVal, oldVal) => {
  console.log(newVal);
  console.log(oldVal);
});

setTimeout(() => {
  user.name = "李四";
  user.age = 100;
}, 2000);
</script>

2. 监听对象属性

<template>
  <span>{{ user.name }}</span> <br />
  <span>{{ user.age }}</span> <br />
</template>

<script setup>
import { reactive, watch } from "vue";

const user = reactive({
  name: "张三",
  age: 20,
});
watch(
  () => user.name,
  (newVal, oldVal) => {
    console.log(newVal);
    console.log(oldVal);
  }
);

setTimeout(() => {
  user.name = "李四";
  user.age = 100;
}, 2000);
</script>

(十) 模板和指令

  1. v-text v-html
  2. 属性绑定
  3. 事件绑定

(十一) 父子组件通信

  1. 父传子
  2. 子传父

1.父传子

// demo.vue
<template>
  <Item title="hello web" />
  <Item :title="title" />
</template>

<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");
</script>

// Item.vue
<template>
  <h3>{{ props.title }}</h3>
</template>

<script setup>
let props = defineProps({
  title: String,
});
</script>

2.子传父

// 父组件
<template>
  <Item :title="title" @getData="changeFn" />
</template>

<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");

const changeFn = (str) => {
  title.value = str;
};
</script>

// 子组件
<template>
  <Item :title="title" @click="handleClick" />
</template>

<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");
const emits = defineEmits(['getData'])
const handleClick = () => {
  emits('getData','从子组件传回消息');
};
</script>

(十二) vue3 路由

  1. 创建路由
  2. 挂载路由
  3. 路由跳转

1.创建路由

/router/index.js

import {createRouter,createWebHashHistory,createWebHistory} from 'vue-router';
const routes = [
    {
        path: '/',
        redirect: '/home'
    },
    {
        path: '/home',
        component: ()=>import('../views/home.vue')
    },
    {
        path: '/about',
        component: ()=>import('../views/about.vue')
    }
]
const router = createRouter({
    history: createWebHistory(),
    routes
})
export default router;

1.挂载路由

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'

const app = createApp(App)
app.use(router)
app.mount('#app')

1.路由跳转

app.vue

<template>
  <router-link to="/home">home</router-link>
  <router-link style="margin-left: 15px;" to="/about">about</router-link>
  <hr />
  <router-view></router-view>
</template>

<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");

const changeFn = (str) => {
  title.value = str;
};
</script>

(十三) Pinia

参考地址

(1) Pinia 简介与基础

1.1 Pinia 简介

官方地址:https://pinia.vuejs.org/ Pinia 是 Vuex4 的升级版,也就是 Vuex5 Pinia 极大的简化了Vuex的使用,是 Vue3的新的状态管理工具 Pinia 对 ts的支持更好,性能更优, 体积更小,无 mutations,可用于 Vue2 和 Vue3 Pinia支持Vue Devtools、 模块热更新和服务端渲染

1.2 Pinia 基础

Vuex 与 Pinia 对比

Vuex 中核心部分: State、Getters、Mutations(同步) 和 Actions(异步) Pinia 中核心部分: State、Getters 和 Actions(同步异步均支持)

Pinia 各部分作用

State: 类似于组件中data,用于存储全局状态 Getters: 类似于组件中的computed,根据已有的State封装派生数据,也具有缓存的特性 Actions: 类似于组件中的methods,用于封装业务逻辑,同步异步均可以

Pinia 官方示例JS版本
import { defineStore } from "pinia";

export const todos = defineStore("todos", {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: "all",
    // type will be automatically inferred to number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // autocompletion! ✨
      return state.todos.filter((todo) => todo.isFinished);
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished);
    },
    // @returns {{ text: string, id: number, isFinished: boolean }[]}
    filteredTodos(state) {
      if (this.filter === "finished") {
        // call other getters with autocompletion ✨
        return this.finishedTodos;
      } else if (this.filter === "unfinished") {
        return this.unfinishedTodos;
      }
      return this.todos;
    },
  },
  actions: {
    // any amount of arguments, return a promise or not
    addTodo(text) {
      // you can directly mutate the stat 00e
      this.todos.push({ text, id: this.nextId++, isFinished: false });
    },
  },
});

(2) Pinia 在Vue3-Vite中的使用

2.1 基础使用流程

① 创建一个vue vite项目

  create-vite@latest
  cd pinia-demo
  npm install
  npm run dev

② 安装 pinia

npm install pinia -S

package.json文件中

 "dependencies": {
    "pinia": "^2.0.9",
    "vue": "^3.2.25"
  },

③ 创建 pinia 实例并挂载到 vue中

// main.ts 文件
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
// 创建 Pinia 实例
const pinia = createPinia()
// 创建 Vue 实例
const app = createApp(App)
// 挂载到 Vue 根实例
app.use(pinia)
app.mount('#app') 

④ 在src文件下创建一个store文件夹,并添加index.ts

// store/index.ts
import { defineStore } from 'pinia'
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例

export const useMainStore =  defineStore('main',{
    // 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
    // 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
    // 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
    state:()=>{
        return {
            info:"pinia 可以使用"
        }
    },
    getters:{},
    actions:{}
})

// 2. 使用容器中的 state
// 3. 通过 getter 修改 state 
// 4. 使用容器中的 action 同步和异步请求

⑤ 在组件中使用

<template>
  <h1>{{ mainStore.info}}</h1>
</template>

<script lang="ts" setup>
import { useMainStore } from "../store";
const mainStore = useMainStore();
</script>

<style>
</style> 

2.2 state 中数据的解构访问

状态管理中

// store/index.ts
state:()=>{
    return {
        info:"pinia 可以使用",
        count:10
    }
}, 

组件中

<template>
  <h1>{{ mainStore.count }}</h1>
  <h1>{{ mainStore.info }}</h1>
  <hr />
  <h1>{{ count }}</h1>
  <h1>{{ info }}</h1>
  <p>
    <button @click="alertData">修改数据</button>
  </p>
</template>

<script lang="ts" setup>
import { toRefs } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from "../store";
const mainStore = useMainStore();
// 解构数据,但是得到的数据是不具有响应式的,只是一次性的
// 相当于仅仅只是...mainStore而已,只是做了reactive处理,并没有做toRefs
// const { count, info } = useMainStore();
// 解决方法:
// 1. 通过使用toRefs函数,因为前面所说相当于是通过reactive处理,因此可以
// const { count, info } = toRefs(mainStore);
// 2. 通过pinia中提供的storeToRefs方法来解决,推荐使用
const { count, info } = storeToRefs(mainStore);
const alertData = () => {
  mainStore.count += 10
}
</script>

<style>
</style> 

2.3 state 中数据的修改方式(actions和组件中)

一般的修改

const alertData = () => {
  // 方式一:最简单的方法,如下
  // 解构后更改方式
  // count.value += 10
  // 结构前更改方式
  // mainStore.count += 10
  // 方式二:若要同时修改多个数据,建议使用$patch来实现批量更新,在内部做了优化
  // mainStore.$patch({
  //   count: mainStore.count + 1,
  //   info: "hello"
  // })
  // 方式三:更好的批量更新方法,通过$patch传递一个函数来实现,这里的state就是useMainStore容器中的state
  mainStore.$patch(state => {
    state.count += 10
    state.info = "pinia批量更新"
  })
} 
通过actions修改
// store/index.ts
// 类似于vue2组件的methods,用于封装业务逻辑,修改state
// // 注意:不能使用箭头函数来定义actions,因为箭头函数绑定外部的this
    actions:{
        changeState (){
            this.count += 10
            this.info = "actions修改数据"
        },
        changeStates (num:number){
            this.count += num + 2
            this.info = "actions修改数据"
        }
    } 
const alertData = () => {
  // 方式一:最简单的方法,如下
  // 解构后更改方式
  // count.value += 10
  // 结构前更改方式
  // mainStore.count += 10
  // 方式二:若要同时修改多个数据,建议使用$patch来实现批量更新,在内部做了优化
  // mainStore.$patch({
  //   count: mainStore.count + 1,
  //   info: "hello"
  // })
  // 方式三:更好的批量更新方法,通过$patch传递一个函数来实现,这里的state就是useMainStore容器中的state
  // mainStore.$patch(state => {
  //   state.count += 10
  //   state.info = "pinia批量更新"
  // })
  // 方式四:通过 actions 来修改数据
  mainStore.changeState()
  mainStore.changeStates(10)
} 

2.4 getters 的使用 定义

// 类似于组件的computed,用来封装计算属性,具有缓存的功能
    getters:{
    	 // 函数接收一个可选参数:state状态对象
        count10(state){
            return state.count += 10
        },
        count10(state){
            return this.count += 10
        },
        // 若使用this.count,则必须指明返回数据的类型
        count11():number{
            return this.count += 11
        }
    }, 

使用

<h1>{{ mainStore.count10 }}</h1>

(3) Pinia 数据持久化

3.1 保存至localStorage中

import { defineStore } from 'pinia';
const useLoginStore = defineStore({
  id: 'login',
  //   state: () => ({
  //     num: 1,
  //   }),
  state: () => ({
    info: 'pinia 可以使用',
  }),
  getters: {},
  actions: {
    alertInfo() {
      this.info = '可以可以,这个秒';
    },
  },
});

// 数据持久化
// 1. 保存数据
const instance = useLoginStore();
instance.$subscribe((_, state) => {
  localStorage.setItem('login-store', JSON.stringify({ ...state }));
});
// 2. 获取保存的数据,先判断有无,无则用先前的
const old = localStorage.getItem('login-store');
if (old) {
  instance.$state = JSON.parse(old);
}
export default useLoginStore; 

使用 插件 pinia-plugin-persist 可以辅助实现数据持久化功能

3.2 安装插件

pnpm install pinia-plugin-persist --save
// main.ts文件中
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import piniaPluginPersist from 'pinia-plugin-persist';
const pinia = createPinia();
pinia.use(piniaPluginPersist);
const app = createApp(App);
app.use(router);
app.use(pinia);
app.mount('#app');

// 接着在对应的 store 里开启 persist 即可。数据默认存在 sessionStorage 里,并且会以 store 的 id 作为 key。

import { defineStore } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
const useLoginStore = defineStore({
  id: 'login',
  //   state: () => ({
  //     num: 1,
  //   }),
  state: () => ({
    info: 'pinia 可以使用',
  }),
  // 开启数据缓存
  persist: {
    enabled: true,
  },
  getters: {},
  actions: {
    alertInfo() {
      this.info = '可以可以,这个秒';
    },
  },
});
export default useLoginStore;

其它设置,自定义保存名称,保存位置和需要保存的数据

// 开启数据缓存
  persist: {
    enabled: true,
    strategies: [
      {
        // 自定义名称
        key: 'login_store',
        // 保存位置,默认保存在sessionStorage
        storage: localStorage,
        // 指定要持久化的数据,默认所有 state 都会进行缓存,你可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
        paths: ['age'],
      },
    ],
  }, 

(十四) vue3抽离业务逻辑

介绍:

在vue中,为了实现项目可维护性与复用的功能,会将代码单独分离出一个功能组件,但是vue2和vue3分离方式并不相同

vue2:

vue2不支持逻辑的抽离,如果想实现抽离必须要将所有html代码与逻辑代码全部抽离,会增加父子组件的通信成本,如果数据过多维护起来会比较头疼
vue3:

vue3支持逻辑的抽离,可以更好的增加项目的可维护性,由于抽离的只是逻辑代码 ,所以没有增加通信成本,就算数据再多,维护起来也非常方便
而且vue3中的 setup 这个函数里面不应该出现大量的逻辑代码,不仅看起来不优美,而且维护起来也有些不方便
在vue3中,一般会将抽离的逻辑代码放进service的文件夹中,然后在父组件中导入使用 

(1) 未分离的写法

  <ul class="goods-list">
    // 直接使用即可
    <li v-for="item in newData" :key="item.id">
      <RouterLink to="/">
        <img :src="item.picture" alt="" />
        <p class="name">{{ item.title }}</p>
        <p class="desc">{{ item.alt }}</p>
      </RouterLink>
    </li>
  </ul>
......
 
// 导入 api 接口
<script>
import { findNew } from '@/api/home'
import { ref } from 'vue'
export default {
  setup () {
    // 获取的数据
    const newData = ref([])
    async function loadNewData () {
      const res = await findNew()
      newData.value = res.data.result
    }
    loadNewData()
    // 不要忘记 return
    return {
      newData
    }
</script>

(2) 抽离写法

功能组件:src/views/Home/service/useHot.js

// 功能组件
import { ref } from 'vue'
// 导入 api 接口
import { findHot } from '@/api/home'
export function useHot () {
  const hotData = ref([])
  async function loadhotData () {
    const res = await findHot()
    hotData.value = res.data.result
  }
  loadhotData()
  // 注意要 return
  return {
    hotData
  }
}

父组件:src/views/Home/index.vue

  <ul class="goods-list">
    // 直接使用即可
    <li v-for="item in hotData" :key="item.id">
      <RouterLink to="/">
        <img :src="item.picture" alt="" />
        <p class="name">{{ item.title }}</p>
        <p class="desc">{{ item.alt }}</p>
      </RouterLink>
    </li>
  </ul>
......
 
// 先导入
<script setup>
import { useHot } from './service/useHot'
export default {
  setup () {
    // 固定写法
    const { hotData } = useHot()
    // 记得 return
    return {
      hotData
    }
  }
</script>

(十五) vue3-admin模板

  1. 地址: https://fantastic-admin.netlify.app/
  2. 克隆项目
  3. 安装依赖
    • 执行 npm i pnpm
    • 执行 pnpm i
    • 执行 npm run dev

(十六) vue3添加@短路径

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve:{
    alias:{
      '@':path.resolve(__dirname,'./src')
    }
  }
})

(十七) Ts配置相关

(1) ts忽略检查

https://www.csdn.net/tags/OtDacg5sODM4NjMtYmxvZwO0O0OO0O0O.html

忽略一行的检查

// @ts-ignore   

(2) ts不识别@别名路径

https://blog.csdn.net/xjtarzan/article/details/123660435

使用TS构建vue3项目时,如果使用例如 import { store } from ‘@/store/user’ 发生红色波浪线报错,说明ts不识别@别名,可以修改 tsconfig.json 文件,添加 baseUrl 和 paths 2个属性:

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
} 

(十八) vite配置-跨域-别名-

注意: vite需要2.x版本

知识点

  1. 跨域配置
  2. 别名配置
  3. 全局css变量
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from 'path';
import { viteMockServe } from "vite-plugin-mock";
export default ({ mode, command }) => {
  const prodMock = false;
  return defineConfig({
    resolve: {
      // 别名配置
      alias: { 
        "@": path.resolve(__dirname, "./src"),
      },
    },
    plugins: [
      vue(),
      // mock配置
      viteMockServe({
        mockPath: "./src/service", // 设置模拟.ts 文件的存储文件夹
        localEnabled: command === "serve", // 设置是否启用本地 xxx.ts 文件,不要在生产环境中打开它.设置为 false 将禁用 mock 功能
        prodEnabled: command !== "serve" && prodMock, // 设置打包是否启用 mock 功能
        supportTs: true, // 打开后,可以读取 ts ⽂件模块。请注意,打开后将⽆法监视.js ⽂件。
        watchFiles: true, // 监视⽂件更改,并重新加载 mock 数据
      }),
    ],

    // 跨域和端口配置
    server: {
      port: 8888,
      proxy: {
        "/api": {
          target: "http://81.71.65.4:5004", // 后端服务实际地址
          changeOrigin: true, //开启代理
          rewrite: (path) => path.replace(/^\/api/, ""),
        },
      },
    },
  });
};

mockServer配置: https://blog.csdn.net/sinat_35082096/article/details/124470571

(十九) 图片懒加载

(1) 使用插件

  1. 安装插件
npm install vue3-lazy -S
  1. 初始化插件
import App from './App.vue'
import lazyPlugin from 'vue3-lazy'

createApp(App)
  .use(lazyPlugin, {
    loading: require('@/assets/images/default.png'), // 图片加载时默认图片
    error: require('@/assets/images/error.png')// 图片加载失败时默认图片
  })
  .mount('#app')
  1. 使用
<ul>
  <li v-for="img in list">
    <img v-lazy="img.src" >
  </li>
</ul>

(2) 自定义指令

// 图片加载不出来时显示的图片
import defaultImg from '@/assets/images/200.png'

const defineDirective = (app) => {
  // 扩展自定义指令
  app.directive('lazyload', {
    // Vue2规则 :vue.directive('lazyload',{ 
                // inserted () {}
               })
    // Vue3规则:mounted
    mounted(el, bindings) {
      // el表示使用指令的DOM元素
      // bindings表示指令相关的信息是一个对象
      // 指令的功能:实现图片的懒加载
      // 1、监听图片进入可视区
      const oberver = new IntersectionObserver(([{ isIntersecting }]) => {
        if (isIntersecting) {
          // 进入了可视区
          // 2、给图片的src属性赋值图片的地址
          el.src = bindings.value
          // 取消图片的监听
          oberver.unobserve(el)
          // 加载的图片失败了,显示默认的图片地址
          el.onerror = () => {
            // 显示默认图片
            el.src = defaultImg
          }
        }
      })
      oberver.observe(el)
    }
  })
}

export default {
  install(app) {
    // 自定义指令
    defineDirective(app)
  }
} 
<template>
  <div class="goods-item">
    <RouterLink to="/" class="image">
      //  v-lazyload取到了后端数据图片地址传给到
      //   IntersectionObserver函数里
      <img v-lazyload="goods.picture" alt="">

    </RouterLink>
    <p class="name ellipsis-2">{{goods.name}}</p>
    <p class="desc ellipsis">{{goods.desc}}</p>
    <p class="price">&yen;{{goods.price}}</p>
    <div class="extra">
      <RouterLink to="/">
        <span>找相似</span>
        <span>发现现多宝贝 &gt;</span>
      </RouterLink>
    </div>
  </div>
</template>

<script>
export default {
  name: 'HomeGoods',
  props: {
    goods: {
      type: Object,
      default: () => {}
    }
  }
}
</script>

https://blog.csdn.net/cmhahaha/article/details/120336211

(十九) defineProps如何添加类型校验

  1. 3.2以上的版本不需要导入defineProps也可以直接使用了

(二十) 后台管理系统自动生成模块代码

(二十一) 修正vetur检查

(1) 方式1 好像没有用

为了解决vscode的vetur插件 把vue3项目当成vue2去检查 然后出现了报错非要template下仍然有要标签的报错

第一步:打开你的package.json

第二步:添加以下代码

  "eslintConfig": {
    "rules": {
      "vue/no-multiple-template-root": "off"
    }
  }

第三步: 重启vsCode

(2) 方式2

解决使用vscode对vue3的template检测报错问题

  1. 打开首选项 -> 设置 -> 搜索vetur valid
  2. 把template, script的检验都取消

(二十二) props解构

(二十三) defineExpose暴露子组件的变量和方法

(1) 父组件

<template>
  <Son ref="sonRef"></Son>
  <span @click="handleClick">显示</span>
</template>
  
<script setup lang='ts'>
import Son from './Son.vue';
import { ref } from 'vue';  
const show = ref(false);
const sonRef = ref()
const handleClick = () => {
  //获取ref中的子组件方法handleNodeClick()
  sonRef.value.handleNodeClick();
  sonRef.value.show = !sonRef.value.show;
} 

</script> 

(2) 子组件

<template>
  <div v-show="show">22222</div>
</template>
  
<script setup lang='ts'>
import { ref } from 'vue'
const show = ref(true);
const handleNodeClick = () => {
  console.log('要执行的方法')
}
//将变量和方法暴露出
defineExpose({show, handleNodeClick })
</script> 

(二十四) reative对象或数组重置

(二十五) defineProps、defineEmits、defineExpose

vue3.2中的defineProps、defineEmits、defineExpose

https://blog.csdn.net/weixin_43550562/article/details/124705539

(1) defineProps

注意: defineProps解构后无法被watch到

<template>
<h1>{{ msg }}</h1>
</template>
<script setuplang="ts">
// 采⽤ts专有声明,⽆默认值
defineProps<{
    msg: string,
    num?: number
}>()
 
// 采⽤ts专有声明,有默认值
interface Props {
  msg?: string
  labels?: string[]
}
const props =withDefaults(defineProps<Props>(),{
      msg:'hello',
      labels:()=>['one','two']
})
 
// ⾮ts专有声明
defineProps({
    msg: String,
    num:{
        type:Number,
        default:''
    }
})
</script> 

Vue3 理解 toRef 和 toRefs 的作用、用法、区别

https://blog.csdn.net/cookcyq__/article/details/121618833

父子组件通信-单向数据流

需求: 后台管理系统, 在列表中点击编辑, 弹出表单, 修改表单(但希望修改表单的时候表格的数据不受影响), 当点击表单确认的时候发请求去后台进行修改, 修改成功后再去更新表格中的内容

(1) 父组件

<template>
  <div>
    <p>{{ form.username }}  {{ form.age }}</p> 
    <hr style="margin-top: 30px;">
    <SonVue :form="form" @submit="onSubmit"/>
  </div>
</template>
  
<script setup lang='ts'>
import SonVue from './Son.vue';
import { ref } from "vue";
const form = ref({
  username: 'zhangsan',
  age: 18
})
const onSubmit = (data)=> { 
  form.value = data;
} 
</script> 

(2) 子组件

<template>
  <div>
    <el-input style="width:200px;" v-model="formData.username"></el-input>
    <el-input v-model="formData.age"></el-input>
    <el-button @click="handleClick">提交</el-button> 
  </div>
</template>
  
<script setup lang='ts'>
import { ref, watchEffect } from "vue"; 

const {form} = defineProps({
  form: Object
})
const emits = defineEmits(['submit'])
const handleClick = () => {
  emits('submit',formData.value);
}

const clone = (obj: Object) => {
  try {
    let str = JSON.stringify(obj);
    return JSON.parse(str);
  } catch (error) {
    return obj;
  }
};

let formData = ref({});
watchEffect(()=> {
  formData.value = clone(form);
})
</script> 

(二十六) vue+ts defineProps自定义类型

<template>
  <div>
    <p>
      <input type="text" :value="form.name">
    </p>
  </div>
</template>
  
<script setup lang='ts'>
import { AAA } from './AAA';

const {form} = defineProps<{
  form: AAA; 
}>();

(二十七) vue3+ts 子组件是表单的父子通信问题

(二十八) ts使用@报错处理

(二十九) vue+ts声明响应式变量指定类型

https://www.jianshu.com/p/08887a101755

const a = ref('') //根据输入参数推导字符串类型 Ref<string>
const b = ref<string[]>([]) //可以通过范型显示约束 Ref<string[]>
const c: Ref<string[]> = ref([]) //声明类型 Ref<string[]> 


const list = ref([1, 3, 5])
console.log('list前:', list.value)
list.value[1] = 7
console.log('list后:', list.value)

type typPeople = {
  name: string
  age: number
}
const list2: Ref<typPeople[]> = ref([])
console.log('list2-前:', list2.value) //{} 不是空数组,而是空对象
list2.value.push({ name: '小张', age: 18 })
console.log('list2-后:', list2.value[0]) //{name: '小张', age: 18}

********* ref 内部值指定类型 *********
const foo = ref<string | number>('foo')
foo.value = 123

********* 如果ref类型未知,则建议将 ref 转换为 Ref<T>: *********

function useState<T>(initial: T) {
  const state = ref(initial) as Ref<T>
  return state
}
const item: typPeople = { name: 'aa', age: 18 }
const x1 = useState(item) // x1 类型为: Ref<typPeople>
const x2 = ref(item) //x2 类型为: Ref<{ name:string; age: number;}>
console.log('ref.value:', x1.value, x1.value.name) 
//Proxy{name: 'aa', age: 18}  aa

(三十) vue2双向数据绑定

/ 父组件
<template>
    <div> 数量: {{num}}</div>
    <!-- <ChildComponent :num="num" @increase="num = $event"/> -->
    <ChildComponent :num.sync="num" />
</template>

//子组件
<template>
    <div @click="addNum"> 接收数量: {{num}}</div>
</template>
<script>
export default {
    props: ['num'],
    // data() {
    //    return {
    //        childNum: this.num
    //    }
    // },
    methods: {
        addNum() {
            // this. childNum++
            // this.$emit('increase', this. childNum)
            this.$emit('update:num', this.num + 1)
        }
    }
}

(三十一) vue3双向数据绑定

(三十二) vue3获取子组件实例

// 父组件
<el-button @click="handleAddAttr">新增</el-button>
<AddAttrVue ref="attrRef"/>
<script setup>
const handleAddAttr = () => { 
  attrRef.value.show = true; 
}
</script>

// 子组件
<template>
	......
</template>
<script setup>
	const show = ref(false);
  defineExpose({
    show,
  })
</script>

(三十三) vue3+ts router配置

(1) 常规配置

  • createRouter创建router实例
  • router的模式分为:
  • createWebHistory -- history模式
  • createWebHashHistory -- hash模式
  • routes的约束类型是RouteRecordRaw
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from '../views/Home.vue';
const routes: Array< RouteRecordRaw > = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
];
 
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});
 
export default router;

(2) 扩展路由额外属性

// 联合类型
type RouteConfig = RouteRecordRaw & {hidden?: boolean}; //hidden 是可选属性
const routes: Array<RouteConfig> = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    hidden: true,
    meta: {
      permission: true,
      icon: ''
    }
  }
];

(三十四) pinia及持久化

http://www.manongjc.com/detail/29-hcjeycqtuguajxb.html

(1) src/store/index.ts

import { createPinia } from "pinia";
import piniaPluginPersist from 'pinia-plugin-persist'

const store = createPinia();
// 持久化
store.use(piniaPluginPersist);

export default store;

(2) src/main.ts

import { createApp } from 'vue'
import store from './store'

const app = createApp(App)
app.use(store)
app.mount('#app')

(3) src/store/user.ts

import { defineStore } from "pinia";

export const useUserStore = defineStore({
  id:'user',
  state() {
    return {
      name: '',
      age: null,
    }
  },
  // 持久化
  persist: {
    enabled: true,
    strategies: [
      {
          key: 'user',
          storage: localStorage,
      }
    ]
  }
})

(三十五) vue3使用全局变量

app.config.globalProperties.$isDev = process.env.NODE_ENV;

import { getCurrentInstance } from "vue";
console.log();

(三十六) 动态组件

参考链接:

https://www.jianshu.com/p/392b03806968

https://blog.csdn.net/qq1195566313/article/details/122891279

(1) vue2动态组件

动态组件是不固定要显示哪个组件,只是有个component标签,表示在模板的相应位置有一个组件, 它具体显示哪个组件,要根据它的is属性来决定, 动态组件多见于tab栏, 根据点击的tab来显示其对应的组件

// 父组件

<template>
  <div>
    <button @click="showCom('A')">显示A</button>
    <button @click="showCom('B')">显示B</button>
    <hr />
    <component :is="currComponent" />
  </div>
</template>

<script>
import ACom from "./A.vue";
import BCom from "./B.vue";
export default {
  components: {
    ACom,
    BCom,
  },
  data() {
    return {
      currComponent: "",
    };
  },

  methods: {
    showCom(name) {
      if (name === 'A') {
        this.currComponent = ACom;
      } else {
        this.currComponent = BCom;
      }
    },
  },
};
</script>

// 子组件A.vue

<template>
  <div>A组件</div>
</template>

// 子组件B.vue

<template>
  <div>B组件</div>
</template>

(2) vue3动态组件

// 一般做法
<template>
  <div>
    <button @click="showCom('A')">显示A</button>
    <button @click="showCom('B')">显示B</button>
    <hr />
    <component :is="currComponent" />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import ACom from "./A.vue";
import BCom from "./B.vue";

const currComponent = ref(null);

const showCom = (name: any) => {
  if (name === "A") {
    currComponent.value = ACom;
  } else {
    currComponent.value = BCom;
  }
};
</script>

注意事项

以上做法能显示效果, 但会弹出一下警告

  1. 在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的

  2. 如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. Component that was made reactive:

这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理

  1. 去处警告只需要用shallowRef替代ref即可

(三十七) Vue3中shallowReactive 与 shallowRef 的用法

参考链接: https://blog.csdn.net/qq_54527592/article/details/119840044

(1) reactive 和 showReactive

<template>
  <div>
    <h1>姓名:{{ person.name }}</h1>
    <h2>年龄:{{ person.age }}</h2>
    <h3>喜欢的水果:{{ person.likeFood.fruits.apple }}</h3>
    <button @click="person.name += '~'">修改姓名</button>
    <button @click="person.age++">修改年龄</button>
    <button @click="person.likeFood.fruits.apple += '!'">修改水果</button>
  </div>
</template>

<script setup lang="ts">
import { reactive, shallowReactive } from "vue";
let person = shallowReactive({
  // 只将第一层数据做了响应式处理
  name: "张三",
  age: 18,
  likeFood: {
    fruits: {
      apple: "苹果", // 深层次的数据将会是一个普通的对象, 不具备响应性
    },
  },
});
</script>

(2) ref 和 showRef

参考链接: https://blog.csdn.net/LiuMH2011/article/details/123716933

  1. ref很容易理解,使用ref创建的对象,里面任意深度的属性与视图都是响应性的

    const data = ref({
        a: {
            b: 1,
        },
    })
    

    当修改b属性的值时,视图会更新

  2. shallowRef就是浅的意思, 与ref不同,shallowRef修改深层属性时,并不会更新视图,如:

    <template>
      <div>
        <p>{{ data.foo }}</p>
        <button @click="update">update</button>
        <button @click="log">log</button>
      </div>
    </template>
    
    <script setup>
    import {shallowRef} from 'vue';
    const data = shallowRef({
      foo: "1",
    });
    
    function update() {
      data.value.foo = "2";
    }
    
    function log() {
      console.log(data.value.foo);
    }
    </script>
    

    上例点击update时,视图并不会更新,但是点击log按钮时,打印出foo的值为2.

    想要更新视图,必须给value赋值,直接替换整个对象。修改update方法:

    function update() {
        data.value = { foo: 200 }
    }
    

    再次点击update按钮,视图更新。

    shalow即浅的意思,shallowRef只有整个数据变更时才刷新视图。

    或者在修改了数据之后,调用triggerRef方法,主动触发视图刷新:

    function update() {
        data.value.foo = '300';
        triggerRef(data);    // 触发视图刷新
    }
    
  3. 为什么要使用shallowRef

    因为ref方法会递归遍历对象的所有属性,使所有属性都具备响应性,所以,当对象很复杂且庞大时,过多的监听会导致性能上的损耗。如假设有一个文章列表数组:

    list = [
        { title: '', auto: '', time: '' },
        { title: '', auto: '', time: '' },
        { title: '', auto: '', time: '' },
        //...
    ]
    

(三十八) 动态样式

vue3支持样式中使用响应式变量

<template>
    <button @click="count+=2">字体大小: {{count}}px</button>
</template>
  
<script setup lang='ts'>
  import {ref} from 'vue';
  const count = ref(10);
</script>
  
<style scoped>
  button {
    font-size: v-bind(count+'px');
  }
</style>

(三十九) ts使用node模块

  1. 安装node的声明文件 npm i @types/node --save-dev

  2. import path from 'path'报错的处理

    给根目录下的tsconfig.node.json添加 "allowSyntheticDefaultImports": true,

(四十) 一个更快的包管理工具pnpm

  1. 安装

    npm i pnpm -g
    
  2. 使用

    pnpm i xxx
    

(四十一) useRoute和useRouter注意事项

  1. 两者只能写在setup中, 如果要在axios中使用, 需要导入router实例

    import router from '@/router/index';
    
  2. 监测当前路由同理

    watch(() => router.currentRoute.value.query,
        (query) => {
          // todo
    }) 
    

    https://blog.csdn.net/weixin_47339511/article/details/117607327

(四十一) 如何给reactive变量赋值

const state = reactive({
  arr: []
});

state.arr = [1, 2, 3]
const state = ref([])
state.value = [1, 2, 3]
const arr = reactive([])
arr.push(...[1, 2, 3])

(四十二) xxxxxx

(四十三) xxxxxx

(四十四) xxxxxx

(四十五) xxxxxx

(四十六) xxxxxx